package com.kxxnzstdsw.di

import com.auth0.jwk.JwkProvider
import com.kxxnzstdsw.config.Config
import com.kxxnzstdsw.config.ENV
import com.kxxnzstdsw.modle.entity.BaseEntity
import com.kxxnzstdsw.modle.entity.BaseEntityDraft
import com.kxxnzstdsw.modle.interceptor.BasicEntityDraftInterceptor
import com.kxxnzstdsw.modle.repo.AccountRepository
import com.kxxnzstdsw.modle.repo.MetadataRepository
import com.kxxnzstdsw.modle.repo.RoutersRepository
import com.kxxnzstdsw.modle.repo.impl.AccountRepositoryImpl
import com.kxxnzstdsw.modle.repo.impl.MetadataRepositoryImpl
import com.kxxnzstdsw.modle.repo.impl.RoutersRepositoryImpl
import com.kxxnzstdsw.schedule.job.HelloJob
import com.kxxnzstdsw.service.AccountService
import com.kxxnzstdsw.service.MetadataService
import com.kxxnzstdsw.service.SystemService
import com.kxxnzstdsw.service.impl.AccountServiceImpl
import com.kxxnzstdsw.service.impl.MetadataServiceImpl
import com.kxxnzstdsw.service.impl.SystemServiceImpl
import com.kxxnzstdsw.utils.jwkProvider
import com.kxxnzstdsw.utils.quartzProperties
import com.zaxxer.hikari.HikariDataSource
import io.lettuce.core.RedisClient
import io.lettuce.core.api.StatefulRedisConnection
import org.babyfish.jimmer.sql.DraftInterceptor
import org.babyfish.jimmer.sql.event.TriggerType
import org.babyfish.jimmer.sql.kt.KSqlClient
import org.babyfish.jimmer.sql.kt.newKSqlClient
import org.babyfish.jimmer.sql.runtime.ConnectionManager
import org.babyfish.jimmer.sql.runtime.Executor
import org.babyfish.jimmer.sql.runtime.SqlFormatter
import org.casbin.adapter.JDBCAdapter
import org.casbin.jcasbin.main.Enforcer
import org.casbin.jcasbin.model.Model
import org.koin.dsl.module
import org.quartz.Scheduler
import org.quartz.impl.StdSchedulerFactory
import org.springframework.jdbc.datasource.DataSourceUtils

val config = module {
  single<Config> {
    val env = System.getenv("ENV") ?: "dev"
    val path = when (env) {
      ENV.Dev.name -> "config.dev.yml"
      ENV.Local.name -> "config.local.yml"
      ENV.Test.name -> "config.test.yml"
      ENV.Prod.name -> "config.prod.yml"
      else -> "config.dev.yml"
    }
    logger.info("当前运行环境:${path}")
    val conf = this::class.java.classLoader.getResourceAsStream(path)?.bufferedReader()?.use { it.readText() }
      ?: throw Exception("配置文件初始化失败")
    Config(conf)
  }
}
val casbin = module {
  single<JDBCAdapter> {
    JDBCAdapter(get<HikariDataSource>())
  }

  single<Model> {
//    rbac模型
    val confModel = """
[request_definition]
r = sub, obj, act

[policy_definition]
p = sub, obj, act

[role_definition]
g = _, _

[policy_effect]
e = some(where (p.eft == allow))

[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
""".trimIndent()

    Model.newModelFromString(confModel).apply {
      addDef("p", "admin", "*, *, *")
    }
  }
  single<Enforcer> {
    Enforcer(get<Model>(), get<JDBCAdapter>())
  }
}

val caffeine = module {

}

val jwt = module {
  single<JwkProvider> {
    get<Config>().jwt.jwkProvider
  }
}

val service = module {
  single<SystemService> { SystemServiceImpl() }
  single<MetadataService> { MetadataServiceImpl() }
  single<AccountService> { AccountServiceImpl() }
}

val repo = module {
  single<AccountRepository> { AccountRepositoryImpl(get()) }
  single<MetadataRepository> { MetadataRepositoryImpl(get()) }
  single<RoutersRepository> { RoutersRepositoryImpl(get()) }
}

val databases = module {
  single<HikariDataSource> {
    val hikariConf = get<Config>().hikari
    HikariDataSource().apply {
      username = hikariConf.username
      password = hikariConf.password
      jdbcUrl = hikariConf.jdbcUrl
      driverClassName = hikariConf.driverClassName
      isAutoCommit = hikariConf.isAutoCommit
      transactionIsolation = hikariConf.transactionIsolation
      validate()
    }
  }
  single<ConnectionManager> {
    ConnectionManager.simpleConnectionManager(get<HikariDataSource>())
  }
  single<DraftInterceptor<BaseEntity, BaseEntityDraft>> {
    BasicEntityDraftInterceptor()
  }
  single<KSqlClient> {
    newKSqlClient {
//      拦截器
      addDraftInterceptors(get<DraftInterceptor<BaseEntity, BaseEntityDraft>>())
//      打印日志
      if (get<Config>().env != ENV.Prod) {
        setExecutor(Executor.log())
        setSqlFormatter(SqlFormatter.PRETTY)
      }
      setTriggerType(TriggerType.BOTH)
      setConnectionManager {
        val dataSource = get<HikariDataSource>()
        val con = DataSourceUtils
          .getConnection(dataSource)
        try {
          proceed(con)
        } finally {
          DataSourceUtils
            .releaseConnection(con, dataSource)
        }
      }
    }
  }
}

val redis = module {
  single<StatefulRedisConnection<String, String>> {
    val redisConf = get<Config>().redis
    val redisClient =
      RedisClient.create("redis://${if (redisConf.password.isEmpty()) "" else "${redisConf.password}@"}${redisConf.host}:${redisConf.port}")
    redisClient.connect()
  }
}

val schedule = module {
  single<StdSchedulerFactory> {
    StdSchedulerFactory(get<HikariDataSource>().quartzProperties(get()))
  }
  single<Scheduler> { get<StdSchedulerFactory>().scheduler }
  single<HelloJob> { HelloJob() }
}

val appModule = listOf(
  config,
  databases,
  redis,
  repo,
  service,
  jwt,
  schedule,
  casbin
)
